package org.ndx.majick.ui;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JComponent;

import org.ndx.majick.properties.Property;
import org.ndx.majick.properties.util.ClassMap;
import org.ndx.majick.ui.bool.BooleanUIProvider;
import org.ndx.majick.ui.enumeration.EnumUIProvider;
import org.ndx.majick.ui.file.FileUIProvider;
import org.ndx.majick.ui.number.NumberUIProvider;
import org.ndx.majick.ui.object.DynamicObjectUIProvider;
import org.ndx.majick.ui.object.ObjectUIProvider;
import org.ndx.majick.ui.string.StringUIProvider;

/**
 * UIProvider is used to generate ui components from bean properties.
 * These components can be customized before usage thanks to properties metadatas
 * @author Nicolas Delsaux
 *
 */
public class UIProvider implements IUIProvider {
	/**
	 * Class internal logger. MUST NOT be used anywhere else, for logs readability
	 */
	private static final Logger logger = Logger.getLogger(UIProvider.class
			.getName());
	
	/**
	 * map associating property classes to class ui providers
	 */
	private ClassMap<IClassUIProvider> providersByClasses = new ClassMap<IClassUIProvider>();
	
	/**
	 * Map linking properties names to metadata. This map provides some default metadatas, global to a whole range of properties
	 */
	private Map<String, IPropertyUIMetadata> namedMetadatas = new TreeMap<String, IPropertyUIMetadata>();
	
	/**
	 * Map linking properties instances to metdata. It's in fact a way to customize by property the way a property UI can be used.
	 */
	private Map<Property<?>, IPropertyUIMetadata> instanceMetadatas = new WeakHashMap<Property<?>, IPropertyUIMetadata>();
	
	public UIProvider() {
		fillProviderByClasses();
	}

	/**
	 * Fill the proider by classes map for all usable data types.
	 * This default implementation fills providers for most common types (string, primitive ones, enum based, ...)
	 */
	protected void fillProviderByClasses() {
		put(Object.class, new DynamicObjectUIProvider(this));
		put(String.class, new StringUIProvider());
		put(Boolean.class, new BooleanUIProvider());
		put(Integer.class, new NumberUIProvider(Integer.class));
		put(Long.class, new NumberUIProvider(Long.class));
		put(Float.class, new NumberUIProvider(Float.class));
		put(Double.class, new NumberUIProvider(Double.class));
		put(File.class, new FileUIProvider());
		put(Enum.class, new EnumUIProvider());
	}
	
	/* (non-Javadoc)
	 * @see org.ndx.majick.ui.IUIProvider#getEditor(org.ndx.majick.properties.Property)
	 */
	public JComponent getEditor(Property<?> source) {
		IClassUIProvider provider = locateProvider(source.getType());
		IPropertyUIMetadata metadata = getMetadata(source);
		return provider.getEditor(metadata, source);
	}
	
	/* (non-Javadoc)
	 * @see org.ndx.majick.ui.IUIProvider#getViewer(org.ndx.majick.properties.Property)
	 */
	public JComponent getViewer(Property<?> source) {
		IClassUIProvider provider = locateProvider(source.getType());
		IPropertyUIMetadata metadata = getMetadata(source);
		return provider.getViewer(metadata, source);
	}

	/* (non-Javadoc)
	 * @see org.ndx.majick.ui.IUIProvider#getMetadata(org.ndx.majick.properties.Property)
	 */
	public IPropertyUIMetadata getMetadata(Property<?> source) {
		if(instanceMetadatas.containsKey(source))
			return instanceMetadatas.get(source);
		return getOrCreateMetadata(source);
	}

	/**
	 * Gets or create metadata associted to a given property object.
	 * @param source property
	 */
	public IPropertyUIMetadata getOrCreateMetadata(Property<?> source) {
		if(!namedMetadatas.containsKey(source.getName())) {
			namedMetadatas.put(source.getName(), locateProvider(source.getType()).createMetadata());
		}
		return namedMetadatas.get(source.getName());
	}

	/**
	 * Locates UI Provider for a given bean class
	 * @param containerBeanClass bean class
	 * @return
	 */
	public IClassUIProvider locateProvider(Class<? extends Object> containerBeanClass) {
		return providersByClasses.locate(containerBeanClass);
	}

	/**
	 * Replace an old ui provider by a new one and return the old one. Additionnaly, some operations details are logged.
	 * @param key
	 * @param value
	 * @return previously associated provider
	 */
	public IClassUIProvider put(Class<? extends Object> key,
			IClassUIProvider value) {
		if(key==null)
			throw new NullPointerException("one tried to associate a ICLassUIProvider to a null class.");
		IClassUIProvider returned = providersByClasses.put(key, value);
		if (logger.isLoggable(Level.FINE))
			logger.log(Level.FINE, key.getName()+" which was previously associated with "+(returned==null ? null : returned.getClass().getName())+
					" is now associated to "+(value==null ? null : value.getClass().getName()));
		return returned;
	}
}
